"""
提供处理和转换参考文献。

函数:
- `convert_reference_format`: 使用正则表达式和AI处理器将给定参考文献的格式转换为所需格式。
- `convert_reference_format_reg`: 使用正则表达式转换参考文献格式。
- `convert_reference_format_ai`: 使用DeepSeek AI模型转换参考文献格式。
- `process_and_correct_field`: 处理并转换给定文件中的引用字段。
- `process_and_convert_references`: 处理给定的文件以转换引用，替换某些模式，并将结果写回文件。
- `convert_bibliography`: 将原始引文数据转换为所需格式。
- `remove_duplicate_references`: 去除引文列表中的潜在重复项。
- `read_file`: 读取文件并返回其内容。
- `write_file`: 将内容写入文件。
- `load_json`: 加载JSON文件并返回其内容。
- `dump_json`: 将数据转储到JSON文件中。

使用说明:
- 此模块用于处理和转换各种格式的参考文献为标准化格式。
- 它结合了基于正则表达式和AI的方法来处理不同的参考文献格式。
- 模块还提供了文件操作和JSON处理的实用函数。
"""

import json
import logging
import re
from collections import Counter
from functools import lru_cache

from main.config import DEFAULT_REFERENCE, API_KEY
from openai import OpenAI

# DeepSeek配置
client = OpenAI(
    api_key=API_KEY,
    base_url="https://api.deepseek.com",
)

# 日志配置
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
)

# 引文处理计数器
reference_counter = Counter()


def convert_reference_format(reference):
    """
    结合正则表达式和 LLM 处理器将给定参考文献的格式转换为所需格式。

    参数:
        reference (str): 要转换的参考文献。

    返回值:
        str: 转换后的参考文献格式。

    异常:
        ValueError: 如果输入的参考文献格式不正确。
        ConnectionError: 如果与AI服务的连接失败。
        TimeoutError: 如果请求超时。
        JSONDecodeError: 如果AI服务返回的JSON格式不正确。
        Exception: 如果处理过程中发生其他错误。
    """
    reg_result = convert_reference_format_reg(reference)

    if reg_result:
        logging.info("Processed with regex: {reference} -> {reg_result}")
        reference_counter["regex"] += 1
        return reg_result

    ai_result = convert_reference_format_ai(reference)
    logging.info("Processed with AI: {reference} -> {ai_result}")
    reference_counter["ai"] += 1
    return ai_result


def convert_reference_format_reg(reference):
    """
    使用正则表达式处理参考文献格式。

    参数:
        reference (str): 要转换的参考文献。

    返回值:
        str: 转换后的参考文献格式，如果转换失败则返回False。

    异常:
        ValueError: 如果输入的参考文献格式不正确。
    """
    try:
        if "[J]" not in reference:
            return False

        # 处理中英文期刊类型的引文
        pattern_cn = r"\[(.*?)\]\.\s*(.*?)\.(.*?),(\d{4})"
        match_cn = re.search(pattern_cn, reference)
        pattern_en = r"(.*?)\.(.*?)\.(.*?)\.(\d{4})"
        match_en = re.search(pattern_en, reference)

        if match_cn:
            authors = match_cn.group(2).split(";")[0].strip()
            year = match_cn.group(4)
            journal = match_cn.group(3)
            first_authors_cn = authors.split(",")[0]
            return f"{first_authors_cn}, {year}, {journal}"

        if match_en:
            first_authors_en = (
                match_en.group(2).split(";")[-1].strip()
                if match_en.group(2)
                else "Anonymous"
            )
            year = match_en.group(4)
            journal = match_en.group(3)
            return f"{first_authors_en}, {year}, {journal}"

        return False

    except ValueError:
        logging.error("Invalid reference format: %s", reference)
        return False


def process_and_correct_field(cite_space, download_cnki_completed, json_file):
    """
    处理并转换引用字段。

    参数:
        cite_space (str): Citespace文件的路径。
        download_cnki_completed (str): 转换后的引用将写入的JSON文件的路径。
        json_file (str): 包含引用的JSON文件的路径。

    返回值:
        tuple: 包含处理后的JSON数据和文本数据的元组。

    异常:
        FileNotFoundError: 如果指定的文件路径不存在。
        IOError: 如果读取或写入文件时发生I/O错误。
        ValueError: 如果JSON文件内容格式不正确。
    """
    try:
        json_data = load_json(json_file)
        for title, references in json_data.items():
            json_data[title] = []
            for ref in references:
                convert_ref = convert_reference_format(ref)
                json_data[title].append(convert_ref)

        dump_json(download_cnki_completed, json_data)
        txt_data = read_file(cite_space)
        txt_data = re.sub(
            r"(AU|AF) ((\w+), )+",
            lambda match: match.group(1)
            + " "
            + "\n   ".join(match.group(2).split(", ")),
            txt_data,
        )
        return json_data, txt_data

    except FileNotFoundError as fnf_error:
        logging.error("FileNotFoundError: %s", fnf_error)
        raise
    except IOError as io_error:
        logging.error("IOError: %s", io_error)
        raise
    except ValueError as ve:
        logging.error("ValueError: %s", ve)
        raise

def convert_reference_format_ai(reference):
    """
    使用 DeepSeek AI模型处理无法通过正则表达式处理的参考文献格式。

    参数:
        reference (str): 要转换的参考文献。

    返回值:
        str: 转换后的参考文献格式。

    异常:
        JSONDecodeError: 如果AI服务返回的JSON格式不正确。
        ValueError: 如果输入的参考文献格式不正确。
        ConnectionError: 如果与AI服务的连接失败。
        TimeoutError: 如果请求超时。
        Exception: 如果处理过程中发生其他错误。
    """

    system_prompt = """
    Context: You are an expert in bibliographic reference formatting.
    Objective: Convert the given reference to the format "FirstAuthor, Year, Source".
    Specifics:
    - FirstAuthor: Extract only first author name.
    - Year: Extract the publication year.
    - Source: Extract the publication source (journal name, book title, etc.).
    Rules:
    - If any information is missing, use "Unknown" for that field.
    - Remove any unnecessary information like edition numbers, page numbers, etc.
    - For translated works, use the original author's name if available.
    - If the reference is in Chinese, extract the information in Chinese characters.
    - If not match everything, use the Default reference format is <<Unknown, 1900, Unknown>>.
    Example:
    - Input_one:"媒介研究[M]. (英) 泰勒, (英) 威利斯, 著.北京大学出版社.2004"
    - Output_one: "泰勒, 2004, 北京大学出版社"
    - Input_two:"符号学[M]. (法) 克里斯蒂娃 (Kristeva,J.) , 著.复旦大学出版社.2014"
    - Output_two: "克里斯蒂娃, 2014, 复旦大学出版社"
    Output: Provide the result in JSON format.
    """

    user_prompt = (
        f"Convert this reference: {reference},"
        f" to the format 'FirstAuthor, Year, Source'."
        f"Only output the result."
    )

    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_prompt},
    ]

    try:
        response = client.chat.completions.create(
            model="deepseek-coder",
            messages=messages,
            response_format={"type": "json_object"},
        )
        result = json.loads(response.choices[0].message.content)
        return f"{result['FirstAuthor']}, {result['Year']}, {result['Source']}"

    except json.JSONDecodeError as je:

        logging.error("JSONDecodeError: %s", je)

        return DEFAULT_REFERENCE
    except ValueError as ve:

        logging.error("ValueError: %s", ve)

        return DEFAULT_REFERENCE

    except ConnectionError as ce:

        logging.error("ConnectionError: %s", ce)

        return DEFAULT_REFERENCE

    except TimeoutError as te:

        logging.error("TimeoutError: %s", te)

        return DEFAULT_REFERENCE

    except Exception as e:

        logging.error("Unexpected error: %s", e)

        return DEFAULT_REFERENCE


def read_file(file_path, mode="r", encoding="utf-8"):
    """
    读取文件并返回其内容。

    参数:
        file_path (str): 要读取的文件路径。
        mode (str): 打开文件的模式，默认为读取模式。
        encoding (str): 编码方式，默认为'utf-8'。

    返回值:
        str: 文件的内容。

    异常:
        无
    """
    with open(file_path, mode, encoding=encoding) as f:
        return f.read()


def write_file(file_path, content, mode="w", encoding="utf-8"):
    """
    将内容写入文件。

    参数:
        file_path (str): 要写入的文件路径。
        content (str): 要写入文件的内容。
        mode (str): 打开文件的模式，默认为写入模式。
        encoding (str): 编码方式，默认为'utf-8'。

    返回值:
        无

    异常:
        无
    """
    with open(file_path, mode, encoding=encoding) as f:
        f.write(content)


def load_json(file_path):
    """
    加载JSON文件并返回其内容。

    参数:
        file_path (str): 要加载的JSON文件的路径。

    返回值:
        dict: JSON文件的内容。

    异常:
        无
    """
    with open(file_path, "r", encoding="utf-8") as f:
        file_content = f.read()
        if not file_content:
            logging.warning("%s is empty.", file_path)
            return {}
        return json.loads(file_content)


def dump_json(file_path, data):
    """
    将数据转储到JSON文件中。

    参数:
        file_path (str): 要转储数据的JSON文件的路径。
        data (dict): 要转储到JSON文件中的数据。

    返回值:
        无

    异常:
        无
    """
    with open(file_path, "w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False, indent=4)


def process_and_convert_references(cite_space, json_file, download_cnki_completed):
    """
    处理给定的文件以转换引用，替换某些模式，并将结果写回文件。

    参数:
        cite_space (str): Citespace文件的路径。
        json_file (str): 包含引用的JSON文件的路径。
        download_cnki_completed (str): 转换后的引用将写入的JSON文件的路径。

    返回值:
        无
    """

    # 调用process_and_correct_field函数处理字段
    json_data, txt_data = process_and_correct_field(
        cite_space, download_cnki_completed, json_file
    )

    # 将处理后的文本数据写回Citespace文件
    write_file(cite_space, txt_data)
    records = read_file(cite_space).split("PT A")

    for record_index, record in enumerate(records):
        ti_pos, so_pos = record.find("TI"), record.find("SO")
        if ti_pos != -1 and so_pos != -1:
            title = record[ti_pos + 3 : so_pos].strip()
            if title in json_data:
                references = json_data[title]
                nr_pos = record.find("NR")
                if references:
                    # CR字段后存在三个空格，然后才是引文
                    record = (
                        record[:nr_pos]
                        + "CR"
                        + "\n".join(f"   {ref}" for ref in references)
                        + "\n"
                        + record[nr_pos:]
                    )
                    # 计算引文数量NR的值
                    record = record.replace("NR 0", f"NR {len(references)}")
                else:
                    record = record[:nr_pos] + "CR 0\n" + record[nr_pos:]
                records[record_index] = record
            else:
                nr_pos = record.find("NR")
                record = record[:nr_pos] + "CR 0\n" + record[nr_pos:]
                records[record_index] = record

    txt_data = "PT A".join(records) + "\n"
    txt_data = re.sub("CR +", "CR ", txt_data)
    write_file("../download_refs_cnki.txt", txt_data)

    # 处理完成后打印统计信息
    logging.info("Processing complete. Statistics:")
    logging.info("References processed with regex: %d.", reference_counter["regex"])
    logging.info("References processed with AI: %d.", reference_counter["ai"])
    logging.info("Total references processed: %d.", sum(reference_counter.values()))


# 2024-08-17 11:30:59,735 - INFO - References processed with regex: 26784
# 2024-08-17 11:30:59,735 - INFO - References processed with AI: 4458
# 2024-08-17 11:30:59,735 - INFO - Total references processed: 31242


def convert_bibliography(raw_data):
    """
    将原始引文数据转换为所需格式。

    参数:
        raw_data (list): 原始引文数据列表。

    返回值:
        list: 转换后的引文数据列表。

    异常:
        无
    """
    converted_data = []
    for item in raw_data:
        parts = item.split(",")
        # 提取 "第一作者、年份、出版社" 字段
        author = parts[0].split(" ")[0]
        year = parts[-2].strip()
        publisher = parts[-1].strip()
        wos_references_format = f"{author}, {year}, {publisher}"
        converted_data.append(wos_references_format)
    return converted_data


def remove_duplicate_references(references):
    """
    以去除引文中潜在的重复项。

    参数:
        references (list): 引文列表。

    返回值:
        list: 处理后的引文列表。

    异常:
        无
    """
    # 创建一个集合来存储处理后的引用
    processed_references = set()

    # 遍历每个引用
    for reference in references:
        # 如果引用不在处理后的引用集合中
        if reference not in processed_references:
            # 将引用添加到处理后的引用集合中
            processed_references.add(reference)

    # 返回处理后的引用列表
    return list(processed_references)


def main():
    """
    主函数
    """

    # 在CiteSpace中转换后的Wos文件
    citespace_wos_file = r"C:\Users\afkxw\Desktop\桌面文件夹\citespace_aigc共被引分析\citespace\download_refworks_to_wos.txt"

    # 在CNKI中下载的参考文献数据
    references_json_file = r"S:\PythonProject\cnki-references-scrapy\main\scrapy_results\CNKI_20240817_02.json"

    # 重构后的参考文献数据
    reconstructed_json_file = "download_refs_cnki_converted.json"
    process_and_convert_references(
        citespace_wos_file, references_json_file, reconstructed_json_file
    )


if __name__ == "__main__":
    main()
